/*
 * disk.cpp
 *
 *  Created on: Jul 11, 2009
 *      Author: mosh
 */
#include "disk.h"

Disk::Disk(){
	this->_disk_fd = open("FILE_SYS", O_RDWR, 0666);
	if(this->_disk_fd < 0){
		this->_disk_fd = open("FILE_SYS", O_RDWR, 0666);

	}
}

Disk::Disk(string file_name){

	string line;
	ifstream myfile (file_name.c_str());
	if (!myfile.is_open()){
		cout << "Unable to open config file";
		exit(1);
	}
	getline(myfile,line);
	char *pInt = (char*)line.c_str() + 15;
	this->_blk_size = atoi(pInt);//read the block size

	getline(myfile,line);
	pInt = (char*)line.c_str() + 17;//read the number of i-nodes
	this->_num_of_inodes = atoi(pInt);

	getline(myfile,line);
	pInt = (char*)line.c_str() + 11;//read the disk size
	this->_disk_size = atoi(pInt);

	this->_new = false;

	cout<<"opening FILE_SYS\n";
	this->_disk_fd = open("FILE_SYS", O_RDWR, 0666);
	if(this->_disk_fd < 0){
		cout<<"creating FILE_SYS\n";
		this->_new = true;
		this->_disk_fd = open("FILE_SYS", O_CREAT | O_RDWR | O_TRUNC, 0666);

		char c[1000] ={0};
		for(int i = 0; i < this->_disk_size/1000; ++i)
			write(this->_disk_fd, c, 1000);

		general_data *g = new general_data;

		this->calcStuff(g);

		int* last = this->set_free_block_list(g);

		int last_free_block_offset = g->blk_size*last[1] + last[0];

		g->last_free_block = last_free_block_offset;

		lseek(this->_disk_fd,0,SEEK_SET);

		write(this->_disk_fd, g, sizeof(general_data));

	}
}

general_data* Disk::read_general_data(){
	return (general_data*)this->read_block(0);
}

void Disk::writeGeneralData(general_data *g){
	lseek(this->_disk_fd,0,SEEK_SET);
	read(this->_disk_fd,g,sizeof(general_data));
}

char *Disk::read_block(int block_number){
	char *data = new char[this->_blk_size];
	int offset = this->_blk_size * block_number;
	lseek(this->_disk_fd,offset,SEEK_SET);
	read(this->_disk_fd,data,this->_blk_size);
	return data;
}
void Disk::write_in_blcok(int block_number,int inBlockOffset,char* input){
	int offset = this->_blk_size*block_number+inBlockOffset;
	lseek(this->_disk_fd,offset,SEEK_SET);
	write(this->_disk_fd,input,sizeof(int));

}


void Disk::write_block(int block_number,char *input){
	int offset = block_number*this->_blk_size;
	lseek(this->_disk_fd,offset,SEEK_SET);
	write(this->_disk_fd,input,this->_blk_size);
}


void Disk::calcStuff(general_data *g){
	g->blk_size = this->_blk_size;
	g->iNodes = this->_num_of_inodes;
	g->numOfBlocks = this->_disk_size / this->_blk_size;
	g->first_free_block = g->blk_size;
	g->root_iNode = NONE;
	g->free_iNodes = g->iNodes;


	int freeBlocks;

	int num_of_iNodes_blocks = (I_NODE_SIZE*g->iNodes) / g->blk_size + 1;
	freeBlocks = g->numOfBlocks - 3 - num_of_iNodes_blocks;

	int number_of_pointers_in_a_block = g->blk_size/sizeof(int);
	g->free_blocks =  freeBlocks * (number_of_pointers_in_a_block -1) / number_of_pointers_in_a_block;


}

int* Disk::set_free_block_list(general_data *g){

	int num_of_iNodes_blocks = (I_NODE_SIZE*g->iNodes) / g->blk_size + 1;
	int freeBlocks = g->numOfBlocks - 3 - num_of_iNodes_blocks;
	int number_of_pointers_in_a_block = g->blk_size/sizeof(int);

	g->free_blocks =  freeBlocks * (number_of_pointers_in_a_block -1) / number_of_pointers_in_a_block;
	int num_of_mediators = freeBlocks / number_of_pointers_in_a_block;

	int first_free_block = 3 + num_of_iNodes_blocks + num_of_mediators;
	int first_mediator = 3 + num_of_iNodes_blocks;

	int *pointers = new int[number_of_pointers_in_a_block];
	int next_free_block = first_free_block;
	int next_mediator = first_mediator;

	bool first = true;

	for(int i = 0; i <= num_of_mediators; ++i){
		int j;
		for(j = 0;j < number_of_pointers_in_a_block - 1 ; ++j){
			if(next_free_block < g->numOfBlocks)
				pointers[j] = next_free_block++;
			else
				pointers[j] = 0;
		}
		if(pointers[j-1] != 0)
			pointers[j] = next_mediator+1;
		else
			pointers[j] = 0;

		if(!first)
			write_block(next_mediator++,(char*)pointers);
		else{
			pointers[j] = next_mediator;
			first = false;
			write_block(1,(char*)pointers);
		}

	}

	int i = 0;
	for(; i < number_of_pointers_in_a_block - 1; ++i){
		if(pointers[i] == 0)
			break;
	}

	pointers = new int[2];
	pointers[0] = i*sizeof(int);
	pointers[1] = next_mediator-1;
	return pointers;

}

int Disk::getFirsFreeBlock(){
	general_data *g = this->read_general_data();
	return g->first_free_block;
}

int Disk::getLastFreeBlock(){
	general_data *g = this->read_general_data();
	return g->last_free_block;
}

char *Disk::read_inode(int i_number){
	int offset = 3 * this->_blk_size + i_number * I_NODE_SIZE;
//	int x = this->_blk_size;
	char *data = new char[I_NODE_SIZE];

	lseek(this->_disk_fd,offset,SEEK_SET);
	read(this->_disk_fd,data,I_NODE_SIZE);
	return data;


}
void Disk::write_inode(int i_number,char *inode){
	int offset = 3 * this->_blk_size + i_number * I_NODE_SIZE;
	lseek(this->_disk_fd,offset,SEEK_SET);
	write(this->_disk_fd,inode,I_NODE_SIZE);
}

void Disk::read_block_to_buffer(int block_number,char *buffer){
	int offset = this->_blk_size * block_number;
	lseek(this->_disk_fd,offset,SEEK_SET);
	read(this->_disk_fd,buffer,this->_blk_size);

}

bool Disk::isNew(){
	return _new;
}

int Disk::getBlockSize(){
	return this->_blk_size;
}

FreeBlockManager::FreeBlockManager(Disk *d){
	this->_disk = d;
}

int FreeBlockManager::pop(){
	general_data *g = this->_disk->read_general_data();
	if(g->free_blocks == 0)
		return NO_FREE_BLOCKS;
	int *pointers = (int *)this->_disk->read_block(1);

	if((unsigned)g->first_free_block == g->blk_size*2 - sizeof(int)){

		int offset = pointers[g->blk_size/sizeof(int) - 1];

		g->first_free_block = g->blk_size;
		char *next_block = this->_disk->read_block(offset);

		char *zero = new char[g->blk_size];
		for(int i = 0;i < g->blk_size; ++i)
			zero[i] = -1;

		this->_disk->write_block(offset,zero);

		this->_disk->write_block(0,(char*)g);
		this->_disk->write_block(1,next_block);
		this->push(offset);
		return this->pop();
	}


	int offset = (g->first_free_block - g->blk_size) / sizeof(int);
	g->first_free_block += sizeof(int);
	g->free_blocks--;
	this->_disk->write_block(0,(char*)g);

	return pointers[offset];
}

int FreeBlockManager::push(int block_number){
	general_data *g = this->_disk->read_general_data();
	int last_block_number = g->last_free_block / g->blk_size;
	int offset = (g->last_free_block % g->blk_size) / sizeof(int);
	int *pointers = (int *)this->_disk->read_block(last_block_number);

	if(offset == 0){
		pointers = (int *)this->_disk->read_block(last_block_number - 1);
		int next_block_number = pointers[g->blk_size/sizeof(int) - 1];
		pointers[0] = block_number;
		for(unsigned int i = 1; i <  g->blk_size/sizeof(int); ++i)
			pointers[i] = -1;
		this->_disk->write_block(next_block_number,(char*)pointers);
		g->last_free_block = next_block_number * g->blk_size + sizeof(int);
	}
	else{

		pointers[offset] = block_number;
		this->_disk->write_block(last_block_number,(char*)pointers);
		g->last_free_block += sizeof(int);
	}

	g->free_blocks++;
	this->_disk->write_block(0,(char*)g);

	return 0;

}

InodeManager::InodeManager(FreeBlockManager *manager,Disk *disk){
	this->_disk = disk;
	this->_manager = manager;
}
int InodeManager::pop(){
	general_data *g = this->_disk->read_general_data();
	if(g->free_iNodes == 0)
		return NO_FREE_INODES;
	int *pointers = (int *)this->_disk->read_block(2);

	if((unsigned)g->first_free_iNode == g->blk_size*3 - sizeof(int)){

		int offset = pointers[g->blk_size/sizeof(int) - 1];

		g->first_free_iNode = 2*g->blk_size;
		char *next_block = this->_disk->read_block(offset);

		char *zero = new char[g->blk_size];
		for(int i = 0;i < g->blk_size; ++i)
			zero[i] = -1;

		this->_disk->write_block(offset,zero);

		int *something = (int*)next_block;

		if( something[g->blk_size/sizeof(int) - 1] == 0){
			g->last_free_iNode_blk -= (g->last_free_iNode_blk/g->blk_size) * g->blk_size;
			g->last_free_iNode_blk += 2*g->blk_size;

		}

		this->_disk->write_block(0,(char*)g);
		this->_disk->write_block(2,next_block);
		this->push(offset);
		return this->pop();
	}

	int offset = (g->first_free_iNode - 2*g->blk_size) / sizeof(int);
	g->first_free_iNode += sizeof(int);
	g->free_iNodes--;
	this->_disk->write_block(0,(char*)g);

	return pointers[offset];


}
int InodeManager::push(int block_number){

	general_data *g = this->_disk->read_general_data();

	int block = g->last_free_iNode_blk/g->blk_size;

	int *pointers = (int*)this->_disk->read_block(block);

	int offset = (g->last_free_iNode_blk%g->blk_size) / sizeof(int);

	if((unsigned)offset == g->blk_size/sizeof(int) - 1){
		int new_block = this->_manager->pop();
		g = this->_disk->read_general_data();
		pointers[g->blk_size/sizeof(int) - 1] = new_block;
		this->_disk->write_block(block,(char*)pointers);
		for(unsigned int i = 1; i < g->blk_size/sizeof(int); ++i)
			pointers[i] = 0 ;
		pointers[0] = block_number;
		this->_disk->write_block(new_block,(char*)pointers);
		g->last_free_iNode_blk = g->blk_size*new_block;
	}
	else{
		pointers[offset] = block_number;
		this->_disk->write_block(block,(char*)pointers);
		g->last_free_iNode_blk += sizeof(int);
	}

	g->free_iNodes++;


	this->_disk->write_block(0,(char*)g);

	return 1;

}

void InodeManager::construct_free_inode_list(){

	if(!this->_disk->isNew())
		return;
	general_data *g = this->_disk->read_general_data();
	int number_of_inodes = g->iNodes;
	int number_of_mediator = number_of_inodes / (g->blk_size/sizeof(int)) + 1;

	int size = g->blk_size/sizeof(int);
	int *pointers = new int[size];
	int next_inode = 0;
	int first_mediator = this->_manager->pop();
	int next_mediator = first_mediator;
	int j,i;
	for(i = 0; i < number_of_mediator; ++i){

		for(j = 0;j < size - 1;j++){
			if(next_inode < number_of_inodes)
				pointers[j] = next_inode++;
			else
				pointers[j] = 0;
		}
		if(i != number_of_mediator - 1){
			pointers[j] = this->_manager->pop();
		}else
			pointers[j] = 0;

		this->_disk->write_block(next_mediator,(char*)pointers);

		next_mediator = pointers[j];

	}

	pointers = (int*)this->_disk->read_block(first_mediator);
	this->_disk->write_block(2,(char*)pointers);
	this->_manager->push(first_mediator);

	next_mediator = first_mediator + number_of_mediator - 1;
	//need some calculation for g
	g = this->_disk->read_general_data();
	g->first_free_iNode = 2*g->blk_size;
	g->last_free_iNode_blk = next_mediator*g->blk_size + sizeof(int)*(number_of_inodes % (g->blk_size/sizeof(int) - 1));

	this->_disk->write_block(0,(char*)g);
}

void printInode(inode*node){
	switch(node->itype){
	case Normal:cout<<"NORMAL"<<endl;break;
	case Directory:cout<<"DIRECTORY"<<endl;break;
	case NoType:cout<<"NO_TYPE"<<endl;break;
	case SoftLink:cout<<"SOFT_LINK"<<endl;
	default:break;
	}
	cout<<(int)node->num_of_hard_link<<endl<<node->file_size<<endl;
	for(int i = 0; i < NUM_OF_DIRECT;++i){
		if(node->direct[i] == NONE)
			cout<<"NONE"<<endl;
		else
			cout<<node->direct[i]<<endl;
	}
	if(node->in_direct == NONE)
		cout<<"NONE"<<endl;
	else
		cout<<(int)node->in_direct<<endl;

}

inode *createInode(){
	inode *node = new inode;
	node->itype = NoType;
	node->file_size = 0;
	node->in_direct = NONE;
	node->num_of_hard_link = 0;
	for(int i = 0; i < NUM_OF_DIRECT;++i)
		node->direct[i] = NONE;
	node->in_direct = NONE;

	return node;
}
